<?php
/**
 * @package base
 */
require_once '_xmlreaderex.php';
require_once '_sitecontext.php';
require_once '_exceptions.php';

/**
 * Codes and translated text manager. Operates above xml files stored
 * in directory Constants::PATH_LOCALIZE.
 * 
 * @uses XMLReaderEx
 */
class CodeManager {
	private $language;
	
	/**
	 * Reference to SiteContext.
	 * @var SiteContext $info
	 */
	private $info;	
	
	/**
	 * Private self instance.
	 * @var CodeManager $codeManager
	 */
	private static $codeManager;
	
	/**
	 * Associative array created using XMLReaderEx
	 * Transported strings from xml file to computer's memory.
	 * @var array $asoc
	 */
	private $asoc;
	
	/**
	 * Private onstructor.
	 * @param SiteContext $info - Context of current session.
	 */
	private function __construct(SiteContext $info) {
		$this->language = $info->getSiteLang()->getLang();
		$this->info = $info;
		$this->asoc = null;
		$this->resCodesDir = NULL;
	}
	
	/**
	 * Get Code Manager which offers API for work with localized text.
	 * 
	 * @return CodeManager
	 */
	public static function getCodeManager(SiteContext $info = null) {
		if (!is_object(CodeManager::$codeManager)) {
			// TODO
			if (is_null($info))
				$info = SiteContext::getSiteContext();
			CodeManager::$codeManager = new CodeManager($info);
		}
		return CodeManager::$codeManager;
	}
	
	/**
	 * Open directory with xml translations
	 * 
	 * @see SiteInf::PATH_LOCALIZE
	 * @return resource
	 */
	private function resourceCodesDir() {
		if ($this->resCodesDir == NULL) {
			$this->resCodesDir = @opendir($this->info->getSiteInf()->getPathFilesystemLocalize());
		}
		return $this->resCodesDir;
	}
	/**
	 * Main xml node.
	 */
	const XMLNODE_CODES = "codes";
	/**
	 * Xml node for every set of strings for one id.
	 */
	const XMLNODE_CODE = "code";
	/**
	 * Xml node for one translated text.
	 */
	const XMLNODE_ITEM = "item";
	
	/**
	 * Substitute {1} {2} ... {n} in text $msg with values from $innerValues
	 * @param string $msg - text to substitute in
	 * @param array $innerValues - values which are substituted into $msg
	 */
	private function substituteInnerValues(string $msg, $innerValues) {
		if ($innerValues !== null) {
			$pattPrefix = "/([^\\\\])\{";
			$pattSuffix = "\}/";
			for ($num = 1; $num <= sizeof($innerValues); ++$num) {
				$patt = $pattPrefix.$num.$pattSuffix;
				$msg = preg_replace($patt, "\${1}".$innerValues[$num - 1], $msg);
			}
			$patt = $pattPrefix."[0-9][0-9]*".$pattSuffix;
			while (preg_match($patt, $msg)) {
				$msg = preg_replace($patt, "\\1", $msg);
			}
		} 
		return $msg;
	}
	/**
	 * Get localized text for <code>what</code>.
	 *
	 * @param string $what
	 * @param array $innerValues - array of strings which are substitutes for every {[:number:]+} in a row
	 * @return string
	 */
	function get(string $what, $innerValues = null) {
		if (isset($this->asoc[self::XMLNODE_CODES][self::XMLNODE_CODE][$what][self::XMLNODE_ITEM][$this->language])) {
			return $this->substituteInnerValues($this->asoc[self::XMLNODE_CODES][self::XMLNODE_CODE][$what][self::XMLNODE_ITEM][$this->language], $innerValues);
		}
		while ($filename = @readdir($this->resourceCodesDir())) {
			if (Policy::possibleCodesFile($filename)) {
				$xml = new XMLReaderEx();
				$xml->open($this->info->getSiteInf()->getPathFilesystemLocalize() . "/" . $filename);
				$anotherAsoc = $xml->codes2Assoc($this->asoc);
				if (isset($this->asoc[self::XMLNODE_CODES][self::XMLNODE_CODE])) {
					foreach ($anotherAsoc[self::XMLNODE_CODES][self::XMLNODE_CODE] as $name => $value) {
						$this->asoc[self::XMLNODE_CODES][self::XMLNODE_CODE][$name] = $value;
					}
				} else {
					$this->asoc = $anotherAsoc;
				}
			} else {
				Logger::getLogger()->debug("$filename is not valid codes xml");
			}
			if (isset($this->asoc[self::XMLNODE_CODES][self::XMLNODE_CODE][$what][self::XMLNODE_ITEM][$this->language])) {
				return $this->substituteInnerValues($this->asoc[self::XMLNODE_CODES][self::XMLNODE_CODE][$what][self::XMLNODE_ITEM][$this->language], $innerValues);
			}
		}
		throw new ExceptionBadCode("Undefined code for '$what'"); // TODO is it neccessary to throw an exception?
	}
}

?>